/* mips.s -- assembly support. */

/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

/* Callee-save $16-$23, $30-$31.
 *
 * On startup, restore regs so retpc === call to a function to start.
 * We're going to call a function ($4) from within this routine.
 * We're passing 3 args, therefore need to allocate 12 extra bytes on
 * the stack for a save area.  The start routine needs a like 16-byte
 * save area.  Must be doubleword aligned (_mips r3000 risc
 * architecture_, gerry kane, pg d-23).
 */

    .globl qt_block
    .globl qt_blocki
    .globl qt_abort
    .globl qt_start
    .globl qt_vstart

    /*
    ** $4: ptr to function to call once curr is suspended
    **    and control is on $7's stack.
    ** $5: 1'th arg to $4.
    ** $6: 2'th arg to $4
    ** $7: sp of thread to suspend.
    **
    ** Totally gross hack: The MIPS calling convention reserves
    ** 4 words on the stack for a0..a3.  This routine "ought" to
    ** allocate space for callee-save registers plus 4 words for
    ** the helper function, but instead we use the 4 words
    ** provided by the function that called us (we don't need to
    ** save our argument registers).  So what *appears* to be
    ** allocating only 40 bytes is actually allocating 56, by
    ** using the caller's 16 bytes.
    **
    ** The helper routine returns a value that is passed on as the
    ** return value from the blocking routine.  Since we don't
    ** touch $2 between the helper's return and the end of
    ** function, we get this behavior for free.
    */
qt_blocki:
    sub $sp,$sp,40        /* Allocate reg save space. */
    sw $16, 0+16($sp)
    sw $17, 4+16($sp)
    sw $18, 8+16($sp)
    sw $19,12+16($sp)
    sw $20,16+16($sp)
    sw $21,20+16($sp)
    sw $22,24+16($sp)
    sw $23,28+16($sp)
    sw $30,32+16($sp)
    sw $31,36+16($sp)
    add $2, $sp,$0        /* $2 <= old sp to pass to func@$4. */
qt_abort:
    add $sp, $7,$0        /* $sp <= new sp. */
    .set noreorder
    jal $31,$4        /* Call helper func@$4 . */
    add $4, $2,$0        /* $a0 <= pass old sp as a parameter. */
    .set reorder
    lw $31,36+16($sp)    /* Restore callee-save regs... */
    lw $30,32+16($sp)
    lw $23,28+16($sp)
    lw $22,24+16($sp)
    lw $21,20+16($sp)
    lw $20,16+16($sp)
    lw $19,12+16($sp)
    lw $18, 8+16($sp)
    lw $17, 4+16($sp)
    lw $16, 0+16($sp)    /* Restore callee-save */

    add $sp,$sp,40        /* Deallocate reg save space. */
    j $31            /* Return to caller. */

    /*
    ** Non-varargs thread startup.
    ** Note: originally, 56 bytes were allocated on the stack.
    ** The thread restore routine (_blocki/_abort) removed 40
    ** of them, which means there is still 16 bytes for the
    ** argument area required by the MIPS calling convention.
    */
qt_start:
    add $4, $16,$0        /* Load up user function pu. */
    add $5, $17,$0        /* ... user function pt. */
    add $6, $18,$0        /* ... user function userf. */
    jal $31,$19        /* Call `only'. */
    j qt_error


    /*
    ** Save calle-save floating-point regs $f20-$f30
    ** See comment in `qt_block' about calling conventinos and
    ** reserved space.  Use the same trick here, but here we
    ** actually have to allocate all the bytes since we have to
    ** leave 4 words leftover for `qt_blocki'.
    **
    ** Return value from `qt_block' is the same as the return from
    ** `qt_blocki'.  We get that for free since we don't touch $2
    ** between the return from `qt_blocki' and the return from
    ** `qt_block'.
    */
qt_block:
    sub $sp, $sp,56        /* 6 8-byte regs, saved ret pc, aligned. */
    swc1 $f20,  0+16($sp)
    swc1 $f22,  8+16($sp)
    swc1 $f24, 16+16($sp)
    swc1 $f26, 24+16($sp)
    swc1 $f28, 32+16($sp)
    swc1 $f30, 40+16($sp)
    sw $31, 48+16($sp)
    jal qt_blocki
    lwc1 $f20,  0+16($sp)
    lwc1 $f22,  8+16($sp)
    lwc1 $f24, 16+16($sp)
    lwc1 $f26, 24+16($sp)
    lwc1 $f28, 32+16($sp)
    lwc1 $f30, 40+16($sp)
    lw $31, 48+16($sp)
    add $sp, $sp,56
    j $31


    /*
    ** First, call `startup' with the `pt' argument.
    **
    ** Next, call the user's function with all arguments.
    ** Note that we don't know whether args were passed in
    ** integer regs, fp regs, or on the stack (See Gerry Kane
    ** "MIPS R2000 RISC Architecture" pg D-22), so we reload
    ** all the registers, possibly with garbage arguments.
    **
    ** Finally, call `cleanup' with the `pt' argument and with
    ** the return value from the user's function.  It is an error
    ** for `cleanup' to return.
    */
qt_vstart:
    add $4, $17,$0        /* `pt' is arg0 to `startup'. */
    jal $31, $18        /* Call `startup'. */

    add $sp, $sp,16        /* Free extra save space. */
    lw $4,  0($sp)        /* Load up args. */
    lw $5,  4($sp)
    lw $6,  8($sp)
    lw $7, 12($sp)
    lwc1 $f12, 0($sp)    /* Load up fp args. */
    lwc1 $f14, 8($sp)
    jal $31,$19        /* Call `userf'. */

    add $4, $17,$0        /* `pt' is arg0 to `cleanup'. */
    add $5, $2,$0        /* Ret. val is arg1 to `cleanup'. */
    jal $31, $16        /* Call `cleanup'. */

    j qt_error
